﻿using System;
using System.Collections.Generic;
using DimensionsHelper.Common.Contracts.Metadata;

namespace DimensionsHelper.Services.Metadata;

public class Property : MetadataNamedObject, IProperty
{
    private bool _boolValue;
    private double _doubleValue;
    private int _intValue;
    private string _stringValue;
    private IProperties? _propertiesValue;

    internal Property(IMetadataDocument document, string name, string context)
        : base(document, ObjectType.Property, name)
    {
        Name = name;
        ValueType = PropertyValueType.Null;
        _stringValue = string.Empty;
        _boolValue = false;
        _intValue = 0;
        _doubleValue = 0;
        _propertiesValue = null;
        Context = context;
    }

    public PropertyValueType ValueType { get; private set; }

    public object? Value => ValueType switch
    {
        PropertyValueType.String => _stringValue,
        PropertyValueType.Bool => _boolValue,
        PropertyValueType.Int => _intValue,
        PropertyValueType.Double => _doubleValue,
        PropertyValueType.Properties => _propertiesValue,
        _ => null,
    };

    public string Context { get; }

    public IDataSource? DataSource { get; internal set; }

    public void SetValue(int value)
    {
        _intValue = value;
        ValueType = PropertyValueType.Int;
    }

    public void SetValue(string? value)
    {
        if (value == null)
        {
            ValueType = PropertyValueType.Null;
            return;
        }

        _stringValue = value;
        ValueType = PropertyValueType.String;
    }

    public void SetValue(bool value)
    {
        _boolValue = value;
        ValueType = PropertyValueType.Bool;
    }

    public void SetValue(double value)
    {
        _doubleValue = value;
        ValueType = PropertyValueType.Double;
    }

    public void SetValue(IProperty property)
    {
        _propertiesValue ??= new Properties(Document, this);
        ((Properties)_propertiesValue).Add(property);
        ((Property)property).Parent = _propertiesValue;
        ValueType = PropertyValueType.Properties;
    }

    public void SetValue(IProperties properties)
    {
        _propertiesValue = properties;
        ((Properties)properties).Parent = this;
        ValueType = PropertyValueType.Properties;
    }

    public T? GetValue<T>(T defaultValue)
    {
        var type = typeof(T);
        var val = Value;

        if (val == null)
        {
            return defaultValue;
        }

        if (type == val.GetType())
        {
            return (T)val;
        }

        if (val is IConvertible convertible)
        {
            return (T)convertible.ToType(type, null);
        }

        if (type == typeof(string))
        {
            if (ValueType == PropertyValueType.String)
            {
                return (T)val;
            }

            return (T)Convert.ChangeType(val, typeof(string));
        }

        if (type == typeof(int))
        {
            return int.TryParse(val.ToString(), out var iVal) ? (T)(object)iVal : defaultValue;
        }

        if (type == typeof(float))
        {
            return float.TryParse(val.ToString(), out var fVal) ? (T)(object)fVal : defaultValue;
        }

        if (type == typeof(double))
        {
            return double.TryParse(val.ToString(), out var dVal) ? (T)(object)dVal : defaultValue;
        }

        if (type == typeof(bool))
        {
            return bool.TryParse(val.ToString(), out var bVal) ? (T)(object)bVal : defaultValue;
        }

        return (T)Convert.ChangeType(val, type);
    }

    public T? GetValue<T>()
    {
        return GetValue(default(T));
    }

    public bool Equals(IProperty? other)
    {
        return other != null && other.Name.Equals(Name, StringComparison.OrdinalIgnoreCase) && 
               ((other.Value == null && Value == null) || 
                (other.Value != null && Value != null && 
                 EqualityComparer<object>.Default.Equals(other.Value, Value)));
    }
}


public class Properties : MetadataContextCollection<IProperty>, IProperties
{
    internal Properties(IMetadataDocument document, IMetadataObject parent)
        : base(document, ObjectType.Properties, parent)
    {
        Unversioned = null;
    }

    internal Properties(IMetadataDocument document) : this(document, document)
    {
    }


    public IProperties? Unversioned { get; internal set; }


    public override IProperty CreateObject(string name)
    {
        return new Property(Document, name, Document.Context);
    }

    public override IProperty CreateObject(string name, string context)
    {
        return new Property(Document, name, context);
    }

    public override IProperty GetByKey(string key)
    {
        var info = GetElementInfoFromKey(key);

        if (!info.IsValid())
        {
            throw new Exception($"关键字'{key}'无效。");
        }

        if (TryGetByKey(key.ToLower(), out var property))
        {
            return property;
        }

        property = new Property(Document, info.Name, info.Context);
        Add(property);

        return property;
    }

    public void SetPropertyValue(string context, string propertyName, string value)
    {
        Get(propertyName, context).SetValue(value);
    }

    public void SetPropertyValue(string context, string propertyName, int value)
    {
        Get(propertyName, context).SetValue(value);
    }

    public void SetPropertyValue(string context, string propertyName, double value)
    {
        Get(propertyName, context).SetValue(value);
    }

    public void SetPropertyValue(string context, string propertyName, bool value)
    {
        Get(propertyName, context).SetValue(value);
    }

    public T? GetPropertyValue<T>(string context, string propertyName, T defaultValue)
    {
        return TryGetByKey(MakeKey(propertyName, context), out var prop)
            ? prop.GetValue(defaultValue)
            : defaultValue;
    }

    public T? GetPropertyValue<T>(string context, string propertyName)
    {
        return GetPropertyValue(context, propertyName, default(T));
    }

    protected override void OnItemAdded(IProperty item)
    {
        base.OnItemAdded(item);
        ((Property)item).Parent = this;
    }
}


public class Note : MetadataNamedObject, INote
{
    internal Note(IMetadataDocument document, string name, string value, string context)
        : base(document, ObjectType.Note, name)
    {
        ObjectType = ObjectType.Note;
        Value = value;
        Context = context;
    }


    public string Value { get; set; }

    public string Context { get; }
}


public class Notes : MetadataContextCollection<INote>, INotes
{
    private const string NoteNamePrefix = "5@note";

    internal Notes(IMetadataDocument document, IMetadataObject parent)
        : base(document, ObjectType.Notes, parent)
    {
    }
    
    public string MakeName()
    {
        return $"{NoteNamePrefix}{Count}";
    }

    public override INote CreateObject(string name)
    {
        return new Note(Document, name, string.Empty, Document.Context);
    }


    public override INote CreateObject(string name, string context)
    {
        return new Note(Document, name, string.Empty, context);
    }


    public void Add(string value, string context)
    {
        var note = new Note(Document, MakeName(), value, context);
        Add(note);
    }


    public void Add(string name, string value, string context)
    {
        var note = new Note(Document, string.IsNullOrEmpty(name) ? MakeName() : name, value, context);
        Add(note);
    }
}